package com.puzzlebazar.client.puzzleinfo;

import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.puzzlebazar.client.main.control.Navigation;
import com.puzzlebazar.client.main.control.PuzzleTokenFactory;
import com.puzzlebazar.client.main.view.RatingDisplay;
import com.puzzlebazar.client.main.view.UpdateTokenCallback;
import com.puzzlebazar.client.user.UserInfo;
import com.puzzlebazar.client.user.UserModel;
import com.puzzlebazar.client.util.AutoHyperlink;
import com.puzzlebazar.client.util.BasicFilter;
import com.puzzlebazar.client.util.Capabilities;
import com.puzzlebazar.client.util.ConnectionLock;
import com.puzzlebazar.client.util.DateUtil;
import com.puzzlebazar.client.util.EnumToString;
import com.puzzlebazar.client.util.Observable;
import com.puzzlebazar.client.util.Observer;
import com.puzzlebazar.client.util.TextUtil;
import com.puzzlebazar.client.util.TokenFactory;
import com.puzzlebazar.client.util.ConnectionLock.UnlockCallback;

public class PuzzleInfoView extends Composite implements Observer, UpdateTokenCallback {

	public final static String SOLVED_NOT_LOGGED_IN = "Login to track your solutions";
	public final static String SOLVED_ERROR = "Error, bad solve information";
	public final static String SOLVED_SOLVED_AND_STARTED = "You solved this puzzle (you also have a partial solution in progress)";
	public final static String SOLVED_SOLVED = "You solved this puzzle";
	public final static String SOLVED_STARTED = "You have a partial solution in progress";
	public final static String SOLVED_NOTHING = "You have not started this puzzle";
	
	public final static String DELETE_PUZZLE = "Delete this puzzle";
	public final static String PUBLISH_NO_REVIEW_PUZZLE = "Publish this puzzle without review";
	public final static String PUBLISH_REVIEW_PUZZLE = "Publish this reviewed puzzle";
	public final static String REVIEW_PUZZLE = "Send this puzzle for review";
	public final static String ACCEPT_PUZZLE = "Accept this puzzle after review";
	public final static String MAKE_PRIVATE_PUZZLE = "Make this puzzle private";

	public final static String DELETE_TUTORIAL = "Delete this tutorial";
	public final static String PUBLISH_NO_REVIEW_TUTORIAL = "Publish this tutorial without review";
	public final static String PUBLISH_REVIEW_TUTORIAL = "Publish this reviewed tutorial";
	public final static String REVIEW_TUTORIAL = "Send this tutorial for review";
	public final static String ACCEPT_TUTORIAL = "Accept this tutorial after review";
	public final static String MAKE_PRIVATE_TUTORIAL = "Make this tutorial private";

	public final static String DELETE_CONFIRMATION_MESSAGE_PUZZLE = "Really delete this puzzle? This action cannot be undone!";
	public final static String DELETE_CONFIRMATION_MESSAGE_TUTORIAL = "Really delete this tutorial? This action cannot be undone!";
	
	public final static String PUBLISH_NO_REVIEW_MESSAGE_PUZZLE = 	"This puzzle has not been reviewed. " +
																	"Publishing it now will bypass the review process and the puzzle " +
																	"will be posted in the non-reviewed area. Are you sure you want to " +
																	"publish this puzzle without review?"; 
	public final static String PUBLISH_NO_REVIEW_MESSAGE_TUTORIAL = "This tutorial has not been reviewed. " +
																	"Publishing it now will bypass the review process and the tutorial " +
																	"will be posted in the non-reviewed area. Are you sure you want to " +
																	"publish this tutorial without review?"; 
	
	private final VerticalPanel mainPanel = new VerticalPanel();
	private final FlexTable infoGrid = new FlexTable();
	private final VerticalPanel buttonPanel = new VerticalPanel();
	
	private final Label refreshing = new Label( "Refreshing..." );
	private final Label invalid = new Label( "Invalid puzzle or tutorial ID!" );
		
	private final Label solved = new Label();
	private final Label genre = new Label();
	private final Label title = new Label();
	private final TextBox titleEdit = new TextBox();
	private final Label author = new Label();
	private final Label size = new Label();
	private final Label status = new Label();
	private final Label validity = new Label();
	private final HorizontalPanel difficulty = createRatingPanel( "bulb/bulb", PuzzleInfo.MAX_DIFFICULTY );
	private final HorizontalPanel quality = createRatingPanel( "star/star", PuzzleInfo.MAX_QUALITY );
	private final Label created = new Label();		
	private final Label edited = new Label();
	private final Label published = new Label();


	private final int solvedRow;
	private final int validityRow;
	private final int qualityRow;
	private final int difficultyRow;
	private final int createdDateRow;
	private final int editedDateRow;
	private final int publishedDateRow;
	
	private final AutoHyperlink playPuzzleLink;
	private final AutoHyperlink viewSolutionLink;
	private final AutoHyperlink viewTutorialLink;
	private final AutoHyperlink continueLink;
	private final AutoHyperlink editLink;
	private final AutoHyperlink editTutorialLink;
	private final HTML deleteLink = new HTML();
	private final HTML publishNoReviewLink = new HTML();
	private final HTML publishReviewLink = new HTML();
	private final HTML reviewLink = new HTML();
	private final HTML acceptLink = new HTML();
	private final HTML makePrivateLink = new HTML();
	
	private final ConnectionLock deletingLock = new ConnectionLock( "deleting" );
	
	private final PuzzleInfoModel model;
	
	/**
	 * Creates a view that displays puzzle information and lets the 
	 * user perform some typical operations on it.
	 * 
	 * @param model The model for the puzzle information to display
	 * @param tokenFactory The factory to use to create the hyperlinks
	 * @param navigation The interface to use to navigate out of the state
	 */
	public PuzzleInfoView( final PuzzleInfoModel model, final PuzzleTokenFactory tokenFactory, final Navigation navigation ) {
		
		this.model = model;
				
		final Label solvedLabel = new Label( "Solved?" );
		final Label genreLabel = new Label( "Genre" );
		final Label titleLabel = new Label( "Title" );
		final Label authorLabel = new Label( "Author" );
		final Label sizeLabel = new Label( "Size" );
		final Label statusLabel = new Label( "Status" );
		final Label validityLabel = new Label( "Validity" );
		final Label difficultyLabel = new Label( "Difficulty" );
		final Label qualityLabel = new Label( "Quality" );
		final Label createdLabel = new Label( "Created" );
		final Label editedLabel = new Label( "Edited" );
		final Label publishedLabel = new Label( "Published" );
		
		int row = 0;
		solvedRow = row;
		infoGrid.setWidget(row,0,solvedLabel);		
		infoGrid.setWidget(row,1,solved);
		row++;		
		infoGrid.setWidget(row,0,genreLabel);		
		infoGrid.setWidget(row,1,genre);
		row++;		
		infoGrid.setWidget(row,0,titleLabel);
		final HorizontalPanel titlePanel = new HorizontalPanel();
		titlePanel.add( title );
		titlePanel.add( titleEdit );
		titleEdit.addKeyPressHandler( new BasicFilter(new char[]{' '}) );
		infoGrid.setWidget(row,1,titlePanel );
		row++;		
		infoGrid.setWidget(row,0,authorLabel);		
		infoGrid.setWidget(row,1,author);
		row++;		
		infoGrid.setWidget(row,0,sizeLabel);		
		infoGrid.setWidget(row,1,size);
		row++;
		infoGrid.setWidget(row,0,statusLabel);		
		infoGrid.setWidget(row,1,status);
		row++;
		validityRow = row;
		infoGrid.setWidget(row,0,validityLabel);		
		infoGrid.setWidget(row,1,validity);
		row++;
		difficultyRow = row;
		infoGrid.setWidget(row,0,difficultyLabel);		
		infoGrid.setWidget(row,1,difficulty);
		row++;
		qualityRow = row;
		infoGrid.setWidget(row,0,qualityLabel);		
		infoGrid.setWidget(row,1,quality);
		row++;
		createdDateRow = row;
		infoGrid.setWidget(row,0,createdLabel);		
		infoGrid.setWidget(row,1,created);
		row++;
		editedDateRow = row;
		infoGrid.setWidget(row,0,editedLabel);		
		infoGrid.setWidget(row,1,edited);
		row++;
		publishedDateRow = row;
		infoGrid.setWidget(row,0,publishedLabel);		
		infoGrid.setWidget(row,1,published);
		row++;
		
		titleEdit.addChangeHandler( new ChangeHandler(){
			@Override
			public void onChange(ChangeEvent event) {
				PuzzleInfoView.this.model.renamePuzzle( titleEdit.getText() ); 				
				PuzzleInfoView.this.model.notifyObservers();
			}} );

		playPuzzleLink = new AutoHyperlink( "Play this puzzle",
				new TokenFactory() {
					@Override
					public String createToken() {
						return tokenFactory.getPlayPuzzleToken(model.getPuzzleKey());
					}
			} );

		viewTutorialLink = new AutoHyperlink( "View this tutorial",
				new TokenFactory() {
					@Override
					public String createToken() {
						return tokenFactory.getViewTutorialToken(model.getPuzzleKey());
					}
			} );
		
		viewSolutionLink = new AutoHyperlink( "View my solution",
				new TokenFactory() {
					@Override
					public String createToken() {
						return tokenFactory.getViewHistoryToken(model.getSolvedHistoryKey());
					}
			} );
		
		continueLink = new AutoHyperlink( "Continue this puzzle",
				new TokenFactory() {
					@Override
					public String createToken() {
						return tokenFactory.getContinueHistoryToken(model.getStartedHistoryKey());
					}
			} );
				
		editLink = new AutoHyperlink( "Edit this puzzle",
				new TokenFactory() {
					@Override
					public String createToken() {
						return tokenFactory.getEditPuzzleToken(model.getPuzzleKey());
					}
			} );

		editTutorialLink = new AutoHyperlink( "Edit this tutorial",
				new TokenFactory() {
					@Override
					public String createToken() {
						return tokenFactory.getEditTutorialToken(model.getPuzzleKey());
					}
			} );
		
		
		deletingLock.addUnlockCallback( new UnlockCallback(){
			@Override
			public void unlocked() {
				navigation.backToPreviousState();
			}		
		});
		deleteLink.addClickHandler( new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				if( !Window.confirm( getDeleteConfirmationMessage() ) ) 
					return;
				model.deletePuzzle(deletingLock);
			}
		});
		
		publishReviewLink.addClickHandler( new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				model.publishPuzzleAfterReview();
				model.notifyObservers();
			}			
		});

		publishNoReviewLink.addClickHandler( new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				if(	!Window.confirm( getPublishNoReviewConfirmationMessage() ) )
					return;
				model.publishPuzzleBeforeReview();
				model.notifyObservers();
			}
		});
		
		reviewLink.addClickHandler( new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				model.sendPuzzleForReview();
				model.notifyObservers();
			}			
		});

		acceptLink.addClickHandler( new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				model.acceptPuzzleAfterReview();
				model.notifyObservers();
			}			
		});

		makePrivateLink.addClickHandler( new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				model.makePuzzlePrivate();
				model.notifyObservers();
			}			
		});
		
		buttonPanel.add( playPuzzleLink );
		buttonPanel.add( viewTutorialLink );
		buttonPanel.add( viewSolutionLink );		
		buttonPanel.add( continueLink );
		buttonPanel.add( editLink );
		buttonPanel.add( editTutorialLink );
		buttonPanel.add( deleteLink );
		buttonPanel.add( publishNoReviewLink );
		buttonPanel.add( publishReviewLink );
		buttonPanel.add( reviewLink );
		buttonPanel.add( acceptLink );
		buttonPanel.add( makePrivateLink );
		
		mainPanel.add( refreshing );
		mainPanel.add( invalid );
		mainPanel.add( infoGrid );
		mainPanel.add( buttonPanel );
		
		refreshing.setVisible(false);
		invalid.setVisible(false);
		infoGrid.setVisible(false);

		mainPanel.setWidth( "100%" );
		mainPanel.setStylePrimaryName( "mainContent" );		
		
		initWidget(mainPanel);

		model.addObserver( this );				
	}

	
	/**
	 * Updates all the widgets contained in this view
	 */
	private void updateWidgets() {
		
		PuzzleInfo puzzleInfo = model.getPuzzleInfo();
		
		if( puzzleInfo == null ) {
			
			infoGrid.setVisible(false);
			buttonPanel.setVisible(false);
			boolean isRefreshing = model.isRefreshing();
			refreshing.setVisible( isRefreshing );
			invalid.setVisible( !isRefreshing );
			
		}
		else {
			UserInfo userInfo  = UserModel.get().getUserInfo();
		
			infoGrid.setVisible(true);
			buttonPanel.setVisible(true);
			refreshing.setVisible(false);
			invalid.setVisible(false);

			solved.setText( getSolvedText( userInfo, puzzleInfo ) );
			genre.setText( puzzleInfo.getGenre() );
			title.setText( puzzleInfo.getTitle() );
			titleEdit.setText( puzzleInfo.getTitle() );
			author.setText( puzzleInfo.getAuthorUsername() );
			size.setText( puzzleInfo.getSize() );
			status.setText( EnumToString.get( puzzleInfo.getStatus() ) );
			validity.setText( EnumToString.get( puzzleInfo.getValidity() ) );
			updateRatingPanel( difficulty, puzzleInfo.getDifficulty() ); 
			updateRatingPanel( quality, puzzleInfo.getQuality() ); 
			created.setText( DateUtil.absoluteDate( puzzleInfo.getDateCreated() ));		
			edited.setText( DateUtil.absoluteDate( puzzleInfo.getDateEdited() ));
			published.setText( DateUtil.absoluteDate( puzzleInfo.getDatePublished() ));
			
			updateTokens();
			
			boolean canPlay            = Capabilities.canPlayPuzzle(userInfo, puzzleInfo);
			boolean canViewTutorial    = Capabilities.canViewTutorial(userInfo, puzzleInfo);
			boolean canViewSolution    = Capabilities.canViewOwnSolutionOfPuzzle(userInfo, puzzleInfo);
			boolean canContinue        = Capabilities.canContinuePuzzle(userInfo, puzzleInfo);
			boolean canEdit            = Capabilities.canEditPuzzle(userInfo, puzzleInfo);
			boolean canEditTutorial    = Capabilities.canEditTutorial(userInfo, puzzleInfo);
			boolean canDelete          = Capabilities.canDeletePuzzleOrTutorial(userInfo, puzzleInfo);
			boolean canRename          = Capabilities.canRenamePuzzleOrTutorial(userInfo, puzzleInfo);
			boolean canPublishNoReview = Capabilities.canPublishPuzzleOrTutorialWithoutReview(userInfo, puzzleInfo);
			boolean canPublishReview   = Capabilities.canPublishPuzzleOrTutorialAfterReview(userInfo, puzzleInfo);
			boolean canAccept          = Capabilities.canAcceptPuzzleOrTutorial(userInfo, puzzleInfo);
			boolean canSendToReview    = Capabilities.canSendPuzzleOrTutorialToReview(userInfo, puzzleInfo);
			boolean canMakePrivate     = Capabilities.canMakePuzzleOrTutorialPrivate(userInfo, puzzleInfo);
			boolean isPublished        = puzzleInfo.isPublished();
			boolean isTutorial         = puzzleInfo.isTutorial();

			infoGrid.getRowFormatter().setVisible( createdDateRow, !isPublished );
			infoGrid.getRowFormatter().setVisible( editedDateRow, !isPublished );
			infoGrid.getRowFormatter().setVisible( publishedDateRow, isPublished );
			infoGrid.getRowFormatter().setVisible( solvedRow, !isTutorial );
			infoGrid.getRowFormatter().setVisible( validityRow, !isTutorial );
			infoGrid.getRowFormatter().setVisible( difficultyRow, !isTutorial );
			infoGrid.getRowFormatter().setVisible( qualityRow, !isTutorial );

			title.setVisible( !canRename );
			titleEdit.setVisible( canRename );
			playPuzzleLink.setVisible( canPlay );
			viewTutorialLink.setVisible( canViewTutorial );
			viewSolutionLink.setVisible( canViewSolution );
			continueLink.setVisible( canContinue );
			editLink.setVisible( canEdit );
			editTutorialLink.setVisible( canEditTutorial );
			deleteLink.setVisible( canDelete );
			publishNoReviewLink.setVisible( canPublishNoReview );
			publishReviewLink.setVisible( canPublishReview );
			reviewLink.setVisible( canSendToReview );
			acceptLink.setVisible( canAccept );
			makePrivateLink.setVisible( canMakePrivate );
			
			if( isTutorial ) {
				deleteLink.setHTML( TextUtil.makeActionLink(DELETE_TUTORIAL) );
				publishNoReviewLink.setHTML( TextUtil.makeActionLink(PUBLISH_NO_REVIEW_TUTORIAL) );
				publishReviewLink.setHTML( TextUtil.makeActionLink(PUBLISH_REVIEW_TUTORIAL) );
				reviewLink.setHTML( TextUtil.makeActionLink(REVIEW_TUTORIAL) );
				acceptLink.setHTML( TextUtil.makeActionLink(ACCEPT_TUTORIAL) );
				makePrivateLink.setHTML( TextUtil.makeActionLink(MAKE_PRIVATE_TUTORIAL) );
			}
			else {
				deleteLink.setHTML( TextUtil.makeActionLink(DELETE_PUZZLE) );
				publishNoReviewLink.setHTML( TextUtil.makeActionLink(PUBLISH_NO_REVIEW_PUZZLE) );
				publishReviewLink.setHTML( TextUtil.makeActionLink(PUBLISH_REVIEW_PUZZLE) );
				reviewLink.setHTML( TextUtil.makeActionLink(REVIEW_PUZZLE) );
				acceptLink.setHTML( TextUtil.makeActionLink(ACCEPT_PUZZLE) );
				makePrivateLink.setHTML( TextUtil.makeActionLink(MAKE_PRIVATE_PUZZLE) );
			}
			
		}
		
	}
	
	/**
	 * Creates a panel presenting a rating, both graphically and textually
	 * 
	 * @param image The image to use for the rating display
	 * @param max The maximum value (the number of graphical widget displayed)
	 * @return The newly created vertical panel
	 */
	private HorizontalPanel createRatingPanel( String image, int max ) {
		final HorizontalPanel panel = new HorizontalPanel();
		final RatingDisplay rating = new RatingDisplay(image);
		rating.setRating( 0 );
		rating.setMax( max );
		rating.updateDisplay();			
		final Label label = new Label( getRatingLabel( 0, max ) ); 
				
				
		panel.add( rating );
		panel.add( label );
		return panel;
	}

	/**
	 * Creates a panel presenting a rating, both graphically and textually
	 * 
	 * @param panel The rating panel to update
	 * @param percent The percentage to display
	 */
	private void updateRatingPanel( HorizontalPanel panel, float percent ) {
		final RatingDisplay rating = (RatingDisplay)panel.getWidget(0);
		final Label label = (Label)panel.getWidget(1);
		rating.setRating( percent );
		rating.updateDisplay();			
		label.setText( getRatingLabel( percent, rating.getMax() ) );
	}

	
	/**
	 * Create a label indicating the current rating
	 * 
	 * @param percent The rating percentage
	 * @param max The maximum rating
	 * @return The string representing the rating, between parenthesis
	 */
	private String getRatingLabel(float percent, int max) {
		return " (" + percent*max + " / " + max + ")";
	}


	@Override
	public void update(Observable observable, Object arg) {
		assert( observable == model );
		updateWidgets();
	}


	@Override
	public void updateTokens() {
		playPuzzleLink.updateTokens();
		viewTutorialLink.updateTokens();
		viewSolutionLink.updateTokens();
		continueLink.updateTokens();
		editLink.updateTokens();
		editTutorialLink.updateTokens();
	}

	@Override
	public void release() {
		model.deleteObserver(this);
	}	


	/**
	 * Get the text to print as the solution status of this puzzle
	 * 
	 * @param userInfo The current user info
	 * @param puzzleInfo The puzzle information
	 * @return The text to print
	 */
	private static String getSolvedText(UserInfo userInfo, PuzzleInfo puzzleInfo) {
		if( userInfo == null )
			return SOLVED_NOT_LOGGED_IN;
		else if( !userInfo.getEmail().equals( puzzleInfo.getSpecificUserEmail()) )
			return SOLVED_ERROR;
		else if( puzzleInfo.isSolvedByUser() ) {
			if( puzzleInfo.isStartedByUser() )
				return SOLVED_SOLVED_AND_STARTED;
			else
				return SOLVED_SOLVED;
		}
		else if( puzzleInfo.isStartedByUser() )
			return SOLVED_STARTED;
		else
			return SOLVED_NOTHING;
		
	}

	/**
	 * @return The confirmation message for deletion
	 */
	private String getDeleteConfirmationMessage() {
		PuzzleInfo puzzleInfo = model.getPuzzleInfo();
		if( puzzleInfo != null && puzzleInfo.isTutorial() )
			return DELETE_CONFIRMATION_MESSAGE_TUTORIAL;
		else
			return DELETE_CONFIRMATION_MESSAGE_PUZZLE;
	}			

	/**
	 * @return The confirmation message for publishing without review
	 */
	private String getPublishNoReviewConfirmationMessage() {
		PuzzleInfo puzzleInfo = model.getPuzzleInfo();
		if( puzzleInfo != null && puzzleInfo.isTutorial() )
			return PUBLISH_NO_REVIEW_MESSAGE_TUTORIAL;
		else
			return PUBLISH_NO_REVIEW_MESSAGE_PUZZLE;
	}			

}
